package com.iagent.factory;

import com.iagent.bean.BannerPrinter;
import com.iagent.bean.Environment;
import com.iagent.bean.IagentBeanWrapper;
import com.iagent.core.ClassPathBeanScanner;
import com.iagent.core.resolver.InterfaceAnnotationResolver;
import com.iagent.core.resolver.NativeInterfaceAnnotationResolver;
import com.iagent.exception.JsonException;
import com.iagent.exception.LogException;
import com.iagent.json.JSON;
import com.iagent.json.JSONSupport;
import com.iagent.json.fastjson.FastJsonSupport;
import com.iagent.json.gson.GsonSupport;
import com.iagent.json.jackson.JacksonSupport;
import com.iagent.logging.LogFactory;
import com.iagent.logging.Logger;
import com.iagent.logging.commons.JakartaCommonsLoggingImpl;
import com.iagent.logging.console.ConsoleImpl;
import com.iagent.logging.jdk14.Jdk14LoggingImpl;
import com.iagent.logging.log4j.Log4jImpl;
import com.iagent.logging.log4j2.Log4j2Impl;
import com.iagent.logging.nologging.NoLoggingImpl;
import com.iagent.logging.slf4j.Slf4jImpl;
import com.iagent.plugins.Interceptor;
import com.iagent.proxy.IagentProxyHandler;
import com.iagent.register.AliasNameRegister;
import com.iagent.register.BeanRegister;
import com.iagent.register.GenericBeanRegister;
import com.iagent.request.HttpExecutor;
import com.iagent.request.RequestConfig;
import com.iagent.resovler.parameter.ParameterResolver;
import com.iagent.resovler.result.*;
import com.iagent.util.Assert;
import com.iagent.util.ClassUtils;
import com.iagent.util.CollectionUtils;
import com.iagent.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * interface agent all Configuration
 */
public final class IagentConfiguration {
    //scanner package
    private String [] basePackages;
    //log type
    private Class<? extends Logger> logImpl;

    private Logger logger = LogFactory.getLogger(IagentConfiguration.class);
    //json type
    private Class<? extends JSONSupport> jsonSupport;
    //default Annotation Handler
    private String handlerName = InterfaceAnnotationResolver.DEFAULT_ANNOTATION_NAME;
    // default request config
    private RequestConfig defaultRequestConfig;

    // File temporary cache path
    private String cachePath = System.getProperty("java.io.tmpdir");

    /**
     * plugin collection
     * @since 2.1.0
     */
    private List<Interceptor> plugins = new ArrayList<>(4);

    /**
     * Store environmental information
     * @since 2.1.0
     */
    private Environment environment = new Environment();
    //interface and proxy object of Map
    private final BeanRegister<Object> iagentRegister = new GenericBeanRegister<>(16);
    //HttpExecutor Map Data
    private final BeanRegister<HttpExecutor> executorRegister = new GenericBeanRegister<>(8);
    //method and IagentBeanWrapper of method Map
    private final BeanRegister<IagentBeanWrapper> beanWrapperRegister = new GenericBeanRegister<>(32);
    //is complete initialize
    private final AtomicBoolean initialize = new AtomicBoolean(false);
    //register alias data
    private final AliasNameRegister<Class> aliasRegister = new AliasNameRegister<>(16);
    // proxy processor
    private final IagentProxyHandler proxyHandler = new IagentProxyHandler(this);
    // proxy class collection
    private final List<Class> proxyClassList = new ArrayList<>(16);
    // Banner info
    private BannerPrinter bannerPrinter = null;
    {
        // logger adapter
        aliasRegister.registerAlias("SLF4J", Slf4jImpl.class);
        aliasRegister.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
        aliasRegister.registerAlias("LOG4J", Log4jImpl.class);
        aliasRegister.registerAlias("LOG4J2", Log4j2Impl.class);
        aliasRegister.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
        aliasRegister.registerAlias("STDOUT_LOGGING", ConsoleImpl.class);
        aliasRegister.registerAlias("NO_LOGGING", NoLoggingImpl.class);
        // JSON adapter
        aliasRegister.registerAlias("FASTJSON", FastJsonSupport.class);
        aliasRegister.registerAlias("GSON", GsonSupport.class);
        aliasRegister.registerAlias("JACKSON", JacksonSupport.class);
        // annotation resolver
        aliasRegister.registerAlias("Native", NativeInterfaceAnnotationResolver.class);
        // result resolver
        aliasRegister.registerAlias("ObjectResultResolver", ObjectResultResolver.class);
        aliasRegister.registerAlias("BinaryResultResolver", BinaryResultResolver.class);
        aliasRegister.registerAlias("BasicResultResolver", BasicResultResolver.class);
        aliasRegister.registerAlias("StringResultResolver", StringResultResolver.class);
    }

    public IagentConfiguration(){
        super();
    }

    /**
     * set base packages
     * @param basePackages
     */
    public IagentConfiguration(String[] basePackages){
        this.basePackages = basePackages;
        init();
    }

    public IagentConfiguration(String basePackages){
        this.basePackages = new String[]{basePackages};
        init();
    }

    public void setBasePackages(String [] basePackages) {
        this.basePackages = basePackages;
    }

    public String getCachePath() {
        return cachePath;
    }

    /**
     * set cache path
     */
    public void setCachePath(String cachePath) {
        if (StringUtils.isEmpty(cachePath)) {
            cachePath = System.getProperty("java.io.tmpdir");
        }
        this.cachePath = cachePath;
    }

    /**
     * set log type
     * @param logImpl logger adapter class
     */
    public void setLogImpl(Class<? extends Logger> logImpl) {
        Assert.notNull(logImpl, "Set logger adapter framework is null");
        if(aliasRegister.containsValue(logImpl)) {
            this.logImpl = logImpl;
            LogFactory.useLogging(this.logImpl);
            logger = LogFactory.getLogger(this.getClass());
        } else {
            logger.warn("Not found Logger adapter framework in alias !");
            throw new LogException("Not found Logger adapter framework in alias register!");
        }
    }

    /**
     * set log type
     * @param logImpl logger adapter alias name
     */
    public void setLogImpl(String logImpl) {
        Assert.notNull(logImpl, "Set logger adapter framework is null");
        Class<? extends Logger> loggerClazz = aliasRegister.getAlias(logImpl);
        if (loggerClazz != null) {
            this.logImpl = loggerClazz;
            LogFactory.useLogging(this.logImpl);
            logger = LogFactory.getLogger(this.getClass());
        } else {
            logger.warn("Not found Logger adapter framework in alias !");
            throw new LogException("Not found Logger adapter framework in alias register!");
        }
    }

    /**
     * set json type
     * @param jsonSupport
     */
    public void setJsonSupport(Class<? extends JSONSupport> jsonSupport) {
        Assert.notNull(jsonSupport, "Set json adapter framework is null");
        if(aliasRegister.containsValue(jsonSupport)) {
            this.jsonSupport = jsonSupport;
            JSON.useJson(this.jsonSupport, true);
        } else {
            logger.warn("Not found Json adapter framework in alias !");
            throw new JsonException("Not found Json adapter framework in alias register!");
        }
    }

    /**
     * set json type
     * @param jsonSupport
     */
    public void setJsonSupport(String jsonSupport) {
        Assert.notNull(jsonSupport, "Set json adapter framework is null");
        Class<? extends JSONSupport> jsonAdapter = aliasRegister.getAlias(jsonSupport);
        if (jsonAdapter != null) {
            this.jsonSupport = jsonAdapter;
            JSON.useJson(this.jsonSupport, true);
        } else {
            logger.warn("Not found Json adapter framework in alias !");
            throw new JsonException("Not found Json adapter framework in alias register!");
        }
    }

    /**
     * get all plugins
     *
     * @return
     * @since 2.1.0
     */
    public List<Interceptor> getPlugins() {
        return plugins;
    }

    /**
     * Loads the specified configuration file path
     *
     * @param path
     * @since 2.1.0
     */
    public void loadProperties(String path) {
        this.environment.load(path);
    }

    public void setBannerPrinter(BannerPrinter bannerPrinter) {
        this.bannerPrinter = bannerPrinter;
    }

    /**
     * initialize package and scanner package
     */
    public void init(){
        Assert.notNull(basePackages, "Framework iagent base packages is null!");
        try {
            if(!initialize.compareAndSet(false, true)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("IAGENT scanner class path is running");
                }
                return;
            }
            if (bannerPrinter == null) {
                logger.warn("No set banner printer, using default banner printer");
                bannerPrinter = new BannerPrinter();
            }
            if (defaultRequestConfig == null) {
                logger.warn("No set request config, using default request config");
                defaultRequestConfig = new RequestConfig(HttpExecutor.DEFAULT_EXECUTOR);
            }
            bannerPrinter.printerBanner();
            long startTime = System.nanoTime();
            // load all plugins
            initializePlugins();
            // load SPI result resolver
            initializeResultResolver();
            // load SPI parameter resolver
            initializeParameterResolver();
            // The interface under the packet is scanned and initialized
            ClassPathBeanScanner scanner = new ClassPathBeanScanner(this);
            scanner.scannerPackages(this.iagentRegister, this.basePackages, this.proxyClassList);
            logger.info("The IAGENT load success, take time [" + 1.0 * (System.nanoTime() - startTime)/1000000 + "] ms, the number of instance is " + this.iagentRegister.size());
        } catch (Throwable t) {
            logger.error("Create interface proxy fail", t);
            throw new RuntimeException(t);
        }

        // using jdk hook method
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                logger.info("IAGENT delete all data");
                plugins.clear();
                aliasRegister.removeAll();
                executorRegister.removeAll();
                beanWrapperRegister.removeAll();
                iagentRegister.removeAll();
            }
        }));
    }

    /**
     * 使用SPI机制初始化结果解析器
     * @since 2.1.0
     */
    private void initializeResultResolver() {
        if (logger.isDebugEnabled()) {
            logger.debug("Start initialize result resolver by Java SPI");
        }
        ServiceLoader<ResultResolver> serviceLoader = ServiceLoader.load(ResultResolver.class);
        for (ResultResolver resultResolver : serviceLoader) {
            if (logger.isDebugEnabled()) {
                logger.debug("Load result resolver class is " + resultResolver.getClass());
            }
            this.aliasRegister.registerAlias(ClassUtils.getClassPathByClass(resultResolver.getClass()), resultResolver.getClass());
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Initialize result resolver end");
        }
    }

    /**
     * 使用SPI机制初始化参数解析器
     * @since 2.2.0
     */
    private void initializeParameterResolver() {
        if (logger.isDebugEnabled()) {
            logger.debug("Start initialize parameter resolver by Java SPI");
        }
        ServiceLoader<ParameterResolver> serviceLoader = ServiceLoader.load(ParameterResolver.class);
        for (ParameterResolver parameterResolver : serviceLoader) {
            if (logger.isDebugEnabled()) {
                logger.debug("Load parameter resolver class is " + parameterResolver.getClass());
            }
            this.aliasRegister.registerAlias(ClassUtils.getClassPathByClass(parameterResolver.getClass()), parameterResolver.getClass());
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Start initialize parameter resolver end");
        }
    }


    /**
     * load all plugins
     *
     * @since 2.1.0
     */
    private void initializePlugins() {
        if (logger.isDebugEnabled()) {
            logger.debug("Start initialize plugin by Java SPI mechanism");
        }
        // 通过使用Java SPI机制加载
        ServiceLoader<Interceptor> interceptorServiceLoader = ServiceLoader.load(Interceptor.class);
        for (Interceptor interceptor : interceptorServiceLoader) {
            if (logger.isDebugEnabled()) {
                logger.debug("Load plugin class is " + interceptor.getClass());
            }
            this.aliasRegister.registerAlias(ClassUtils.getClassPathByClass(interceptor.getClass()), interceptor.getClass());
        }
        // 手动注入到别名管理
        List<Class<Interceptor>> interceptorClazz = this.aliasRegister.getAliasClassListByClass(Interceptor.class);
        CollectionUtils.sortByOrder(interceptorClazz);
        for (Class<Interceptor> clazz : interceptorClazz) {
            if (logger.isDebugEnabled()) {
                logger.debug("Load plugin class is " + clazz);
            }
            this.plugins.add(ClassUtils.newInstance(clazz));
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Initialize plugin end");
        }
    }

    /**
     * get proxy object by interface class
     *
     * @param cls
     * @param <T>
     * @return
     */
    protected <T> T getIagentBean(Class<T> cls){
        return this.iagentRegister.getBeanByClass(ClassUtils.getClassPathByClass(cls), cls);
    }

    public String getHandlerName() {
        return handlerName;
    }

    public RequestConfig getDefaultRequestConfig() {
        return defaultRequestConfig;
    }

    public void setDefaultRequestConfig(RequestConfig defaultRequestConfig) {
        this.defaultRequestConfig = defaultRequestConfig;
    }

    /**
     * 拿到所有别名注解起
     * @return
     */
    public AliasNameRegister<Class> getAliasRegister() {
        return aliasRegister;
    }

    /**
     * 获取所有的执行器实例
     * @return
     */
    public BeanRegister<HttpExecutor> getExecutorRegister() {
        return executorRegister;
    }

    /**
     * 获取所有method 实例
     * @return
     */
    public BeanRegister<IagentBeanWrapper> getBeanWrapperRegister() {
        return beanWrapperRegister;
    }

    /**
     * 设置注解处理器
     * @param name
     */
    public void setHandlerName(String name) {
        if (aliasRegister.containBeanName(name) &&
                InterfaceAnnotationResolver.class.isAssignableFrom(aliasRegister.getBeanObject(name))) {
            this.handlerName = name;
        } else {
            this.handlerName = InterfaceAnnotationResolver.DEFAULT_ANNOTATION_NAME;
        }
    }

    public IagentProxyHandler getProxyHandler() {
        return proxyHandler;
    }

    public List<Class> getProxyClassList() {
        return proxyClassList;
    }

    public String[] getBasePackages() {
        return basePackages;
    }

    public Environment getEnvironment() {
        return environment;
    }
}
